/*
 * @(#)SourceToHTMLConverter.java 1.19 04/03/28
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. SUN
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.tools.doclets.internal.toolkit.util;

import com.sun.tools.doclets.internal.toolkit.*;
import com.sun.javadoc.*;
import java.io.*;
import java.util.*;

/**
 * Converts Java Source Code to HTML.
 * 
 * This code is not part of an API. It is implementation that is subject to
 * change. Do not use it as an API
 * 
 * @author Jamie Ho
 * @since JDK 1.4
 */
public class SourceToHTMLConverter {
	
	/**
	 * The background color.
	 */
	protected static final String BGCOLOR = "white";
	
	/**
	 * The line number color.
	 */
	protected static final String LINE_NO_COLOR = "green";
	
	/**
	 * The number of trailing blank lines at the end of the page. This is
	 * inserted so that anchors at the bottom of small pages can be reached.
	 */
	protected static final int NUM_BLANK_LINES = 60;
	
	/**
	 * Source is converted to HTML using static methods below.
	 */
	private SourceToHTMLConverter() {
	}
	
	/**
	 * Convert the Classes in the given RootDoc to an HTML.
	 * 
	 * @param configuration the configuration.
	 * @param rd the RootDoc to convert.
	 * @param outputdir the name of the directory to output to.
	 */
	public static void convertRoot(Configuration configuration, RootDoc rd,
			String outputdir) {
		if (rd == null || outputdir == null) {
			return;
		}
		PackageDoc[] pds = rd.specifiedPackages();
		for (int i = 0; i < pds.length; i++) {
			convertPackage(configuration, pds[i], outputdir);
		}
		ClassDoc[] cds = rd.specifiedClasses();
		for (int i = 0; i < cds.length; i++) {
			convertClass(configuration, cds[i], getPackageOutputDir(outputdir,
					cds[i].containingPackage()));
		}
	}
	
	/**
	 * Convert the Classes in the given Package to an HTML.
	 * 
	 * @param configuration the configuration.
	 * @param pd the Package to convert.
	 * @param outputdir the name of the directory to output to.
	 */
	public static void convertPackage(Configuration configuration,
			PackageDoc pd, String outputdir) {
		if (pd == null || outputdir == null) {
			return;
		}
		String classOutputdir = getPackageOutputDir(outputdir, pd);
		ClassDoc[] cds = pd.allClasses();
		for (int i = 0; i < cds.length; i++) {
			convertClass(configuration, cds[i], classOutputdir);
		}
	}
	
	/**
	 * Return the directory write output to for the given package.
	 * 
	 * @param outputDir the directory to output to.
	 * @param pd the Package to generate output for.
	 */
	private static String getPackageOutputDir(String outputDir, PackageDoc pd) {
		return outputDir + File.separator
				+ DirectoryManager.getDirectoryPath(pd) + File.separator;
	}
	
	/**
	 * Convert the given Class to an HTML.
	 * 
	 * @param configuration the configuration.
	 * @param cd the class to convert.
	 * @param outputdir the name of the directory to output to.
	 */
	public static void convertClass(Configuration configuration, ClassDoc cd,
			String outputdir) {
		if (cd == null || outputdir == null) {
			return;
		}
		File file;
		SourcePosition sp = cd.position();
		if (sp == null || (file = sp.file()) == null) {
			return;
		}
		try {
			LineNumberReader reader = new LineNumberReader(new FileReader(file));
			int lineno = 1;
			String line;
			StringBuffer output = new StringBuffer();
			while ((line = reader.readLine()) != null) {
				output.append(formatLine(line, configuration.sourcetab, lineno));
				lineno++;
			}
			output = addLineNumbers(output.toString());
			output.insert(0, getHeader());
			output.append(getFooter());
			writeToFile(output.toString(), outputdir, cd.name(), configuration);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Write the output to the file.
	 * 
	 * @param output the string to output.
	 * @param outputDir the directory to output to.
	 * @param className the name of the class that I am converting to HTML.
	 * @param configuration the Doclet configuration to pass notices to.
	 */
	private static void writeToFile(String output, String outputDir,
			String className, Configuration configuration) throws IOException {
		File dir = new File(outputDir);
		dir.mkdirs();
		File newFile = new File(dir, className + ".html");
		configuration.message.notice("doclet.Generating_0", newFile.getPath());
		FileOutputStream fout = new FileOutputStream(newFile);
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fout));
		bw.write(output);
		bw.close();
		fout.close();
	}
	
	/**
	 * Given a <code>String</code>, add line numbers.
	 * 
	 * @param s the text to add line numbers to.
	 * 
	 * @return the string buffer with the line numbering for each line.
	 */
	private static StringBuffer addLineNumbers(String s) {
		StringBuffer sb = new StringBuffer();
		StringTokenizer st = new StringTokenizer(s, "\n", true);
		int lineno = 1;
		String current;
		while (st.hasMoreTokens()) {
			current = st.nextToken();
			sb.append(current.equals("\n")
					? getHTMLLineNo(lineno) + current
					: getHTMLLineNo(lineno) + current + st.nextToken());
			lineno++;
		}
		return sb;
	}
	
	/**
	 * Get the header.
	 * 
	 * @return the header to the output file
	 */
	protected static String getHeader() {
		StringBuffer result = new StringBuffer("<HTML>" + DocletConstants.NL);
		result.append("<BODY BGCOLOR=\"" + BGCOLOR + "\">" + DocletConstants.NL);
		result.append("<PRE>" + DocletConstants.NL);
		return result.toString();
	}
	
	/**
	 * Get the footer
	 * 
	 * @return the footer to the output file
	 */
	protected static String getFooter() {
		StringBuffer footer = new StringBuffer();
		for (int i = 0; i < NUM_BLANK_LINES; i++) {
			footer.append(DocletConstants.NL);
		}
		footer.append("</PRE>" + DocletConstants.NL + "</BODY>"
				+ DocletConstants.NL + "</HTML>" + DocletConstants.NL);
		return footer.toString();
	}
	
	/**
	 * Get the HTML for the lines.
	 * 
	 * @param lineno The line number
	 * @return the HTML code for the line
	 */
	protected static String getHTMLLineNo(int lineno) {
		StringBuffer result = new StringBuffer("<FONT color=\"" + LINE_NO_COLOR
				+ "\">");
		if (lineno < 10) {
			result.append("00" + ((new Integer(lineno)).toString()));
		} else if (lineno < 100) {
			result.append("0" + ((new Integer(lineno)).toString()));
		} else {
			result.append((new Integer(lineno)).toString());
		}
		result.append("</FONT>    ");
		return result.toString();
	}
	
	/**
	 * Format a given line of source. <br>
	 * Note: In the future, we will add special colors for constructs in the
	 * language.
	 * 
	 * @param line the string to format.
	 * @param tabLength the number of spaces for each tab.
	 * @param currentLineNo the current number.
	 */
	protected static String formatLine(String line, int tabLength,
			int currentLineNo) {
		if (line == null) {
			return null;
		}
		StringBuffer lineBuffer = new StringBuffer(Util.escapeHtmlChars(line));
		// Insert an anchor for the line
		lineBuffer.append("<a name=\"line." + Integer.toString(currentLineNo)
				+ "\"></a>");
		lineBuffer.append(DocletConstants.NL);
		Util.replaceTabs(tabLength, lineBuffer);
		return lineBuffer.toString();
	}
	
	/**
	 * Given an array of <code>Doc</code>s, add to the given
	 * <code>HashMap</code> the line numbers and anchors that should be
	 * inserted in the output at those lines.
	 * 
	 * @param docs the array of <code>Doc</code>s to add anchors for.
	 * @param hash the <code>HashMap</code> to add to.
	 */
	protected static void addToHash(Doc[] docs, HashMap hash) {
		if (docs == null) {
			return;
		}
		for (int i = 0; i < docs.length; i++) {
			hash.put(new Integer(docs[i].position().line()), getAnchor(docs[i]));
		}
	}
	
	/**
	 * Given a <code>Doc</code>, return an anchor for it.
	 * 
	 * @param d the <code>Doc</code> to check.
	 * @return an anchor of the form &lt;a name="my_name">&lt;/a>
	 */
	protected static String getAnchor(Doc d) {
		return "    <a name=\"" + getAnchorName(d) + "\"></a>";
	}
	
	/**
	 * Given a <code>Doc</code>, return an anchor name for it.
	 * 
	 * @param d the <code>Doc</code> to check.
	 * @return the name of the anchor.
	 */
	public static String getAnchorName(Doc d) {
		return "line." + d.position().line();
	}
}
